Passed
Pull Request — master (#164)
by Jan
03:39 queued 01:45
created

file-write.ts ➔ getTmpFileAsync   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
1
import {
2
    fsReadPromise,
3
    fsRenamePromise,
4
    fsUnlinkPromise,
5
    fsWritePromise,
6
    getNextBufferSubarrayAsync,
7
    getNextBufferSubarraySync,
8
    processFile,
9
    processFileAsync
10
} from "./util-file"
11
import * as tmp from 'tmp'
12
import * as path from 'path'
13
import * as fs from 'fs'
14
import { findId3TagPosition, getId3TagSize } from "./id3-tag"
15
16
const FileBufferSize = 20 * 1024 * 1024
17
18
export function writeId3TagToFileSync(filepath: string, id3Tag: Buffer) {
19
    const tmpFile = getTmpFilePathSync(filepath)
20
    processFile(filepath, 'r', (readFileDescriptor) => {
21
        processFile(tmpFile, 'w', (writeFileDescriptor) => {
22
            fs.writeSync(writeFileDescriptor, id3Tag)
23
            streamOriginalIntoNewFileSync(readFileDescriptor, writeFileDescriptor)
24
        })
25
    })
26
    fs.unlinkSync(filepath)
27
    fs.renameSync(tmpFile, filepath)
28
}
29
30
export function writeId3TagToFileAsync(filepath: string, id3Tag: Buffer, callback: (err: Error|null) => void) {
31
    getTmpFileAsync(filepath, (err, tmpFile) => {
32
        if(err || !tmpFile) {
33
            return callback(err)
34
        }
35
36
        processFileAsync(filepath, 'r', async (readFileDescriptor) => {
37
            return processFileAsync(tmpFile, 'w', async (writeFileDescriptor) => {
38
                await fsWritePromise(writeFileDescriptor, id3Tag)
39
                await streamOriginalIntoNewFileAsync(readFileDescriptor, writeFileDescriptor)
40
            })
41
        }).then(async () => {
42
            await fsUnlinkPromise(filepath)
43
            await fsRenamePromise(tmpFile, filepath)
44
        }).catch((error) => {
45
            callback(error)
46
        })
47
    })
48
}
49
50
function getTmpFilePathSync(filepath: string): string {
51
    const parsedPath = path.parse(filepath)
52
    return tmp.tmpNameSync({
53
        tmpdir: parsedPath.dir,
54
        template: `${parsedPath.base}.tmp-XXXXXX`,
55
    })
56
}
57
58
function getTmpFileAsync(filepath: string, callback: tmp.TmpNameCallback) {
59
    const parsedPath = path.parse(filepath)
60
    tmp.tmpName({
61
        tmpdir: parsedPath.dir,
62
        template: `${parsedPath.base}.tmp-XXXXXX`,
63
    }, (err, filename) => {
64
        callback(err, filename)
65
    })
66
}
67
68
function streamOriginalIntoNewFileSync(readFileDescriptor: number, writeFileDescriptor: number) {
69
    const buffer = Buffer.alloc(FileBufferSize)
70
    let data
71
    while((data = getNextBufferSubarraySync(readFileDescriptor, buffer)).length) {
72
        const id3TagPosition = findId3TagPosition(data)
73
        if(id3TagPosition !== -1) {
74
            data = getBufferWithoutId3TagAndSkipSync(readFileDescriptor, data, id3TagPosition)
75
        }
76
        fs.writeSync(writeFileDescriptor, data, 0, data.length, null)
77
    }
78
}
79
80
async function streamOriginalIntoNewFileAsync(readFileDescriptor: number, writeFileDescriptor: number) {
81
    const buffer = Buffer.alloc(FileBufferSize)
82
    let data
83
    while((data = await getNextBufferSubarrayAsync(readFileDescriptor, buffer)).length) {
84
        const id3TagPosition = findId3TagPosition(data)
85
        if(id3TagPosition !== -1) {
86
            data = await getBufferWithoutId3TagAndSkipAsync(readFileDescriptor, data, id3TagPosition)
87
        }
88
        await fsWritePromise(writeFileDescriptor, data, 0, data.length, null)
89
    }
90
}
91
92
function getBufferWithoutId3TagAndSkipSync(fileDescriptor: number, data: Buffer, id3TagPosition: number): Buffer {
93
    const dataFromId3Start = data.subarray(id3TagPosition)
94
    const id3TagSize = getId3TagSize(dataFromId3Start)
95
    if(id3TagSize > dataFromId3Start.length) {
96
        const missingBytesCount = id3TagSize - dataFromId3Start.length
97
        fs.readSync(
98
            fileDescriptor,
99
            Buffer.alloc(missingBytesCount),
100
            0,
101
            missingBytesCount,
102
            null
103
        )
104
        return data.subarray(0, id3TagPosition)
105
    }
106
107
    const id3TagEndPosition = id3TagPosition + id3TagSize
108
    return Buffer.concat([
109
        data.subarray(0, id3TagPosition),
110
        data.subarray(id3TagEndPosition)
111
    ])
112
}
113
114
async function getBufferWithoutId3TagAndSkipAsync(fileDescriptor: number, data: Buffer, id3TagPosition: number): Promise<Buffer> {
115
    const dataFromId3Start = data.subarray(id3TagPosition)
116
    const id3TagSize = getId3TagSize(dataFromId3Start)
117
    if(id3TagSize > dataFromId3Start.length) {
118
        const missingBytesCount = id3TagSize - dataFromId3Start.length
119
        await fsReadPromise(
120
            fileDescriptor,
121
            Buffer.alloc(missingBytesCount),
122
            0,
123
            missingBytesCount,
124
            null
125
        )
126
        return data.subarray(0, id3TagPosition)
127
    }
128
129
    const id3TagEndPosition = id3TagPosition + id3TagSize
130
    return Buffer.concat([
131
        data.subarray(0, id3TagPosition),
132
        data.subarray(id3TagEndPosition)
133
    ])
134
}